﻿using AntMovement.Comm.Model;
using AntMovement.Core.LabelService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace AntMovement.Comm
{
    public class TagParsing : ITagParsing
    {
        private Func<long, bool> _detection;
        private Action<VariableModel> _action;
        public TagParsing(Func<long, bool> func, Action<VariableModel> action)
        {
            _action = action;
            _detection = func;
        }

        public void AddTag(string sourceLabel, object obj)
        {
            Type sourceClass = obj.GetType();
            if (sourceClass.IsClass)
            {
                AddModel(sourceLabel, default, obj, 0, true, false);
                Analyze(sourceLabel, sourceClass, obj);
            }
        }

        private void Analyze(string sourceLabel, Type type, object obj)
        {
            var properties = GetAccessableProperties(type);
            foreach (var property in properties)
            {
                string label = $"{sourceLabel}.{property.Name}";
                if (property.PropertyType.IsArray)
                {
                    AddModel(label, property, obj, 0, true, true);
                    Array array = (Array)property.GetValue(obj, null);
                    for (int i = 0; i < array.Length; i++)
                    {
                        Type typeSub = array.GetValue(i).GetType();
                        if (typeSub.IsClass)
                        {
                            Analyze($"{label}[{i}]", typeSub, array.GetValue(i));
                        }
                        else
                        {
                            AddModel($"{label}[{i}]", default, array, i, false, true);
                        }
                    }
                }
                else
                {
                    if (property.PropertyType.IsClass)
                    {
                        if (property.PropertyType.Name == "String")
                        {
                            AddModel(label, property, obj, 0, false, false);
                        }
                        else
                        {
                            AddModel(label, property, property.PropertyType, 0, true, false);
                            Analyze(label, property.PropertyType, property.PropertyType);
                        }
                    }
                    else
                    {
                        AddModel(label, property, obj, 0, false, false);
                    }
                }
            }
        }

        private void AddModel(string label, PropertyInfo property, object obj, int index, bool isclass, bool isarray)
        {
            long hash = label.GetHashCode();
            if (!_detection.Invoke(hash))
            {
                string datatype = property != null ? PropertyType(property.PropertyType.Name) : PropertyType(obj.GetType().Name);
                _action.Invoke(new VariableModel()
                {
                    Source = obj,
                    Hash = hash,
                    Label = label,
                    Property = property,
                    IsClass = isclass,
                    IsArray = isarray,
                    Index = index,
                    DataType = property != null ? property.PropertyType.Name : obj.GetType().Name,
                });
            }
        }

        private string PropertyType(string str)
        {
            string[] datatypes = new string[] { "Int32", "UInt32", "Single", "Int64", "UInt64", "Double", "Int16", "UInt16", "Byte", "Boolean", "String" };
            if (!datatypes.Any(x => str.Contains(x)))
            {
                return "class";
            }
            if (str.Contains("[]"))
            {
                return str.Replace("[]", default);
            }
            return str;
        }

        private IEnumerable<PropertyInfo> GetAccessableProperties(Type classType)
        {
            return classType
#if NETSTANDARD1_3
                .GetTypeInfo().DeclaredProperties.Where(p => p.SetMethod != null);
#else
                .GetProperties(
                    BindingFlags.SetProperty |
                    BindingFlags.Public |
                    BindingFlags.Instance)
                .Where(p => p.GetSetMethod() != null);
#endif

        }
    }
}
